﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SimpleData.ObjectModel.Rules;
using System.Collections.Specialized;
using SimpleData.Infrastructure.Tenancy;
using SimpleData.ObjectModel.Collections;
using System.Diagnostics.Contracts;
using System.Globalization;

namespace SimpleData.ObjectModel.Design
{
	/// <summary>
	/// Descrição de um campos do documento
	/// </summary>
	public class FieldDescriptor : IObjectPerTenant // IRuleSchematic,
	{
		private FieldTypeDescriptor type;
		private FlagCollection<FieldProperties> properties = new FlagCollection<FieldProperties>();

		#region [ .ctor ]

		/// <summary>
		/// Utilizado pelo EF
		/// </summary>
		protected FieldDescriptor()
		{
			this.Rules = new FieldRuleCollection( this );

			// TODO : alterar para o definitivo
			this.ApplicationID = 1;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <param name="ns"></param>
		/// <param name="fieldType"></param>
		public FieldDescriptor( string name, string ns, FieldTypeDescriptor fieldType )
			: this( new FieldName( name, ns ), fieldType )
		{ }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <param name="fieldType"></param>
		public FieldDescriptor( FieldName name, FieldTypeDescriptor fieldType )
			: this()
		{
			Contract.Requires<ArgumentNullException>( name != null, "name" );
			Contract.Requires<ArgumentNullException>( fieldType != null, "fieldType" );

			this.Name = name;
			this.TypeName = fieldType.Name;
			this.type = fieldType;
		}

		#endregion

		/// <summary>
		/// Obtém o ID que foi criado unicamente para o campo
		/// </summary>
		public long FieldID
		{ get; protected set; }

		/// <summary>
		/// 
		/// </summary>
		public long ApplicationID
		{ get; set; }

		/// <summary>
		/// Obtém ou define o nome que identifica o campo unicamente no sistema
		/// </summary>
		public FieldName Name
		{ get; private set; }

		/// <summary>
		/// Obtém ou define o nome que é exibido para o usuário
		/// </summary>
		public string DisplayName
		{ get; set; }

		/// <summary>
		/// Obtém ou define a instrução de preenchimento de um campo
		/// </summary>
		public string ToolTipText
		{ get; set; }

		/// <summary>
		/// Obtém ou define o tipo que é armazenado no campo
		/// </summary>
		public FieldTypeName TypeName
		{ get; private set; }

		/// <summary>
		/// Obtém ou define o tipo de dados do campo
		/// </summary>
		/// <remarks>Uma vez criado o campo, o tipo não poderá ser alterado</remarks>
		//[Obsolete("Utilizar o provider definido em \"Runtime.FieldTypes\".")]
		//public FieldTypeDescriptor Type
		//{
		//    get
		//    {
		//        if ( this.type == null )
		//            throw new NotImplementedException( "Fazer o processo que carrega o tipo do campo" );
		//        return this.type;
		//    }
		//}

		/// <summary>
		/// Obtém ou define as flags de determinam o comportamento do campo
		/// </summary>
		internal long Properties
		{
			get { return this.properties.Data; }
			set { this.properties.Data = value; }
		}

		/// <summary>
		/// Indica que o campo é pesquisável
		/// </summary>
		public bool Searchable
		{
			get { return this.properties.Is( FieldProperties.Searchable ); }
			set
			{
				if ( value )
					this.properties.Set( FieldProperties.Searchable );
				else
					this.properties.Unset( FieldProperties.Searchable );
			}
		}

		/// <summary>
		/// Indica que o campo serve somente para ser armazenado
		/// </summary>
		public bool Storage
		{
			get { return this.properties.Is( FieldProperties.Storage ); }
			set
			{
				if ( value )
					this.properties.Set( FieldProperties.Storage );
				else
					this.properties.Unset( FieldProperties.Storage );
			}
		}

		/// <summary>
		/// Obtém ou define de qual schema o campo pertence
		/// </summary>
		public Schema Schema
		{ get; internal set; }

		/// <summary>
		/// Propriedade utilizada pelo EF
		/// </summary>
		internal List<Rule> RulesWrapper
		{
			get { return this.Rules.RulesWrapper; }
		}

		/// <summary>
		/// Obtém as regras que devem ser aplicadas ao campo
		/// </summary>
		public FieldRuleCollection Rules
		{ get; private set; }

		/// <example>
		/// Na implementação de um script de validação, o script pode verificar se esse campo possui o metadado e fazer um tipo de validação
		/// </example>
		//public NameValueCollection Metadata
		//{	// TODO : Verifica se há necessidade desse campo
		//    get;
		//    private set;
		//}

		/// <summary>
		/// Alterar o campo para um novo tipo
		/// </summary>
		/// <param name="fieldType"></param>
		public void ChangeType( FieldTypeDescriptor fieldType )
		{
			Contract.Requires<ArgumentNullException>( fieldType != null, "fieldType" );

			this.TypeName = fieldType.Name;
			this.Rules.SetFieldType( fieldType.NewTypeRule() );
		}

		#region [ class FieldRuleCollection ]

		/// <summary>
		/// Coleção de regras do campo
		/// </summary>
		public sealed class FieldRuleCollection : ICollection<FieldRule>
		{
			private List<Rule> rules = new List<Rule>();


			internal FieldRuleCollection( FieldDescriptor field )
			{
				this.Field = field;
			}

			/// <summary>
			/// 
			/// </summary>
			public FieldDescriptor Field
			{ get; private set; }

			/// <summary>
			/// Obtém ou define a coleção de regras do campo
			/// </summary>
			internal List<Rule> RulesWrapper
			{ get; private set; }

			/// <summary>
			/// 
			/// </summary>
			/// <param name="rule"></param>
			internal void SetFieldType( FieldTypeRule rule )
			{
				// somente pode haver uma "rule" que representa 
				// o tipo e suas configurações para o campo
				var toRemove = this.rules.OfType<FieldTypeRule>().SingleOrDefault();
				if ( toRemove != null )
					this.rules.Remove( toRemove );
				this.rules.Insert( 0, rule );
			}

			public FieldTypeRule FieldType
			{
				get { return this.rules.OfType<FieldTypeRule>().Single(); }
			}

			#region ICollection<FieldRule> Members

			public void Add( FieldRule item )
			{
				this.rules.Add( item );
			}

			public void Clear()
			{
				// Remove todas as regras, exceto a do tipo "FieldTypeRule";
				var keepRule = this.rules.OfType<FieldTypeRule>().SingleOrDefault();

				this.rules.Clear();
				if ( keepRule != null )
					this.rules.Add( keepRule );
			}

			public bool Contains( FieldRule item )
			{
				return this.rules.Contains( item );
			}

			public void CopyTo( FieldRule[] array, int arrayIndex )
			{
				this.rules.OfType<FieldRule>().ToArray().CopyTo( array, arrayIndex );
			}

			public int Count
			{
				get { return this.rules.OfType<FieldRule>().Count(); }
			}

			public bool IsReadOnly
			{
				get { return false; }
			}

			public bool Remove( FieldRule item )
			{
				return this.rules.Remove( item );
			}

			#endregion

			#region IEnumerable<FieldRule> Members

			public IEnumerator<FieldRule> GetEnumerator()
			{
				return this.rules.OfType<FieldRule>().GetEnumerator();
			}

			#endregion

			#region IEnumerable Members

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return this.GetEnumerator();
			}

			#endregion
		}

		/// <summary>
		/// Coleção de regras de um campo
		/// </summary>
		//public sealed class FieldRuleCollection : CollectionBase<FieldRule>
		//{

		//    public FieldRuleCollection( FieldDescriptor field )
		//    {
		//        this.Field = field;
		//    }

		//    public FieldDescriptor Field
		//    {
		//        get;
		//        private set;
		//    }


		//}

		#endregion
	}
}
